package com.password.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Base64;
import java.util.UUID;

import com.password.exception.ManagerException;
import com.password.secure.SecretSign;
import com.password.secure.SecretSign.SignObject;
import com.password.secure.SecretSign.VerifyObject;
import com.password.util.ManagerExceptionUtil.Status;

/**
 * 文件加密工具类，
 * 可以考虑压缩后再加密，加密后再压缩[暂时没有实现，要压缩自己进行压缩即可]
 * @author yzh
 * @date 2020年5月27日
 *
 */
public class FileEncrptUtil {
	/* 掩码：咖啡大猫 */
	public static byte[] MASK = new byte[] { (byte) 0xCA, (byte) 0xFE, (byte) 0xDA, (byte) 0xCA };
	public static final byte RAS_MODEL = 1;// RAS模式
	public static final byte DES_MODEL = 2; // DES模式
	public static final byte DES_RAS_MODEL = 3; // 混合模式[RAS加密des秘钥的形式]
	public static final String path = System.getProperty("user.home");

	/**
	 * 文件加密
	 * <pre>
	 * 加密文档格式如下所示：
	 * 4个字节的掩码，CA,FE,DA,CA
	 * 1个字节的加密模式；
	 * 2个字节DES秘钥长度，注：RAS模式下，这二个字节都为0
	 * RSA加密后的秘钥数据，：注：RAS模式，没有DES秘钥数据
	 * 2个字节的签名公钥长度；
	 * 加密后的签名公钥数据
	 * 2个字节的签名后的长度
	 * 文档签名信息
	 * 4个字节的文档长度
	 * 加密文档信息
	 * </pre>
	 * 
	 * @author yzh
	 * @date 2020年6月1日
	 *  
	 * @param publicKey
	 * @param desKey
	 * @param in
	 * @param out
	 * @throws Exception
	 */
	public static void fileEncrpt(String publicKey, byte[] desKey, InputStream in, OutputStream out) throws Exception {
		int available = in.available();
		if (available > 0) {
			if (available > SystemConfig.getRSAInBlock() * SystemConfig.getRSAFileEncrotyNum()) {
				// 使用混合模式加密
				encrptForDesRAS(publicKey, in, out);
			} else {
				// 使用RSA加密
				encrptForRsa(publicKey, in, out);
			}
		} else {
			throw new ManagerException(ManagerExceptionUtil.noAvailableDataReadStatus);
		}
	}

	/**
	 * 文件解密
	 * @author yzh
	 * @date 2020年5月28日
	 *  
	 * @param desKey des秘钥
	 * @param in
	 * @param out
	 * @throws ManagerException
	 * @throws IOException 
	 */
	public static void fileDecrypt(String privateKey, InputStream in, OutputStream out)
			throws ManagerException, IOException {
		OutputStream desOutputStream = null;
		try {
			int available = in.available();
			if (available < 11) {
				throw new ManagerException(ManagerExceptionUtil.encrptyFileFormatStatus);
			}
			// 掩码校验
			verifyMask(in);
			// 加密模式
			int read = in.read();
			// 获取DES秘钥的长度
			int keySize = keySize(in);
			// RAS加密模式
			if (read == RAS_MODEL && keySize == 0) {
				byte[] key = Base64.getDecoder().decode(privateKey);
				// 获取签名公钥
				byte[] signPublicKey = decrptSignPublicKey(in, key);
				// 获取签名校验类
				VerifyObject verifyObject = SecretSign.getVerifyObject(signPublicKey);
				// 获取签名长度
				int signLen = keySize(in);
				byte[] signData = new byte[signLen];
				in.read(signData);
				// 获取数据长度
				int dataSize = dataSize(in);
				byte[] data = new byte[dataSize];
				int dataSize2 = in.read(data);
				if (dataSize != dataSize2) {
					throw new ManagerException(ManagerExceptionUtil.dataErrorStatus);
				}
				byte[] decrypt = RSAEncryptUtil.decrypt(data, key);
				// 校验数据
				verifyObject.update(decrypt, 0, decrypt.length);
				verifyObject.verify(signData, 0, signData.length);
				out.write(decrypt);
			} else if (read == DES_RAS_MODEL) {
				// 获取key
				byte[] desKey = getDesKey(keySize, privateKey, in);
				// 获取签名公钥
				byte[] signPublicKey = decrptSignPublicKey(in, Base64.getDecoder().decode(privateKey));
				// 获取签名校验类
				VerifyObject verifyObject = SecretSign.getVerifyObject(signPublicKey);
				// 获取签名长度
				int signLen = keySize(in);
				byte[] signData = new byte[signLen];
				in.read(signData);
				// 获取数据长度
				int dataSize = dataSize(in);
				if (dataSize <= 0)
					throw new ManagerException(ManagerExceptionUtil.dataErrorStatus);
				desOutputStream = DesUtil.getDESOutputStream(out, desKey);
				byte[] buffer = new byte[SystemConfig.getDesEncrptBufferSize()];
				int bufferLen = 0;
				while ((bufferLen = in.read(buffer)) > 0) {
					desOutputStream.write(buffer, 0, bufferLen);
					verifyObject.update(buffer, 0, bufferLen);
				}
				// 验签
				verifyObject.verify(signData, 0, signData.length);
			} else {
				throw new ManagerException(ManagerExceptionUtil.noSuchEncrptyModelStatus);
			}
		} catch (ManagerException e) {
			throw e;
		} catch (Exception e) {
			throw new ManagerException(Status.BuildExceptionStatus("500", e.getMessage()));
		} finally {
			in.close();
			out.close();
			if (desOutputStream != null)
				desOutputStream.close();
		}

	}

	/**
	 * 获取解密后的签名公钥
	 * @author yzh
	 * @date 2020年6月1日
	 *  
	 * @param in
	 * @param key
	 * @return
	 * @throws IOException
	 * @throws Exception
	 */
	private static byte[] decrptSignPublicKey(InputStream in, byte[] key) throws IOException, Exception {
		int signPublicKeyLen = keySize(in);
		byte[] signPublicKeyEncypted = new byte[signPublicKeyLen];
		in.read(signPublicKeyEncypted);
		return RSAEncryptUtil.decrypt(signPublicKeyEncypted, key);
	}

	private static byte[] getDesKey(int keySize, String privateKey, InputStream in) throws Exception {
		byte[] enDesKey = new byte[keySize];
		int read = in.read(enDesKey);
		if (read == keySize) {
			byte[] decode = Base64.getDecoder().decode(privateKey);
			return RSAEncryptUtil.decrypt(enDesKey, decode);
		} else {
			throw new ManagerException(ManagerExceptionUtil.dataErrorStatus);
		}
	}

	/**
	 * 校验魔数
	 * @author yzh
	 * @date 2020年5月28日
	 *  
	 * @param in
	 * @throws IOException
	 */
	private static void verifyMask(InputStream in) throws IOException {
		byte[] mask = new byte[4];
		in.read(mask);
		if (!Arrays.equals(MASK, mask)) {
			throw new ManagerException(ManagerExceptionUtil.maskVerifyErrorStatus);
		}
	}

	/**
	 * 获取data长度
	 * @author yzh
	 * @date 2020年5月28日
	 *  
	 * @param in
	 * @return
	 * @throws IOException 
	 */
	private static int dataSize(InputStream in) throws IOException {
		byte[] b = new byte[4];
		in.read(b);
		return byteToLong(b);
	}

	/**
	 * 获取加密key的长度值
	 * @author yzh
	 * @date 2020年5月28日
	 *  
	 * @param in
	 * @return
	 * @throws IOException 
	 */
	private static int keySize(InputStream in) throws IOException {
		byte[] b = new byte[2];
		in.read(b);
		return byteToLong(b);
	}

	private static int byteToLong(byte[] data) {
		int result = 0;
		for (int i = 0; i < data.length; i++) {
			result += (data[i] & 0xFF) << (i * 8);
		}
		return result;
	}

	/**
	 * 使用RSA加密
	 * @author yzh
	 * @date 2020年5月28日
	 *  
	 * @param publicKey RSA公钥
	 * @param in
	 * @param out
	 * @throws Exception 
	 */
	private static void encrptForRsa(String publicKey, InputStream in, OutputStream out) throws Exception {
		byte[] decoded = Base64.getDecoder().decode(publicKey);
		try {
			byte[] data = new byte[in.available()];
			in.read(data, 0, in.available());
			byte[] encrype = RSAEncryptUtil.encrype(data, decoded);
			// 写入掩码
			out.write(MASK); // 掩码,4个字节
			out.write(RAS_MODEL); // 加密模式，1个字节
			// 秘钥长度为0，不需要存储秘钥,2个字节存储
			writeSize(out, 0, 2);
			// 随机的签名公钥存储
			SignObject signObj = writeSignPublic(publicKey, out);
			signObj.update(data, 0, data.length);
			// 签名信息
			byte[] sign = signObj.sign();
			writeSize(out, sign.length, 2);
			out.write(sign);
			// 存储数据字节数，4个字节
			writeSize(out, encrype.length, 4);
			// 写入加密后的数据
			out.write(encrype);
		} finally {
			in.close();
			out.close();
		}
	}

	/**
	 * 使用混合模式加密
	 * @author yzh
	 * @date 2020年5月28日
	 *  
	 * @param publicKey RSA公钥
	 * @param in
	 * @param out
	 * @throws Exception 
	 */
	private static void encrptForDesRAS(String publicKey, InputStream in, OutputStream out) throws Exception {
		InputStream desInputStream = null;
		try {
			// 生成des密钥
			byte[] desKey = DesUtil.randGenerateDESKey();
			// 获取加密流
			desInputStream = DesUtil.getDESInputStream(in, desKey);
			// 写入掩码
			out.write(MASK); // 掩码,4个字节
			out.write(DES_RAS_MODEL); // 加密模式，1个字节
			// 生成随机的des秘钥，并写入加密文件中
			writeKeyData(publicKey, desKey, out);
			// 随机的签名公钥存储
			SignObject signObj = writeSignPublic(publicKey, out);
			// 签名数据长度以及数据
			File tempRootPath = SystemConfig.getTempPath();
			// 先将数据写到缓存中
			String tempName = UUID.randomUUID().toString();
			File tempPath = new File(tempRootPath, tempName);
			tempPath.createNewFile();
			try (OutputStream bufferOut = new FileOutputStream(tempPath)) {
				byte[] data = new byte[SystemConfig.getDesEncrptBufferSize()];
				int dataSize = 0;
				while ((dataSize = desInputStream.read(data)) > 0) {
					bufferOut.write(data, 0, dataSize);
					signObj.update(data, 0, dataSize);
				}
			}
			// 签名信息
			byte[] sign = signObj.sign();
			writeSize(out, sign.length, 2);
			out.write(sign);
			// 将缓存中的数据写入加密文件中
			try (InputStream bufferInt = new FileInputStream(tempPath)) {
				// 写入数据大小
				int available = bufferInt.available();
				writeSize(out, available, 4);
				// 写入加密数据
				byte[] data = new byte[4098];
				int dataSize = 0;
				// 写入加密后的数据
				while ((dataSize = bufferInt.read(data)) > 0) {
					out.write(data, 0, dataSize);
				}
			}
			// 删除缓存文件
			tempPath.delete();
		} finally {
			in.close();
			out.close();
			if (desInputStream != null)
				desInputStream.close();
		}
	}

	/**
	 * 将size转换成len个字节，并写入out中
	 * 注：len为2或者4
	 * @author yzh
	 * @date 2020年6月1日
	 *  
	 * @param out
	 * @param size
	 * @param len
	 * @throws IOException
	 */
	private static void writeSize(OutputStream out, int size, int len) throws IOException {
		if (len == 4) {
			byte low1 = (byte) (0xFF & size); // 低位字节1
			byte low2 = (byte) (0xFF & size >> 8); // 低位字节2
			byte hight1 = (byte) (0xFF & size >> 16); // 高位字节1
			byte hight2 = (byte) (0xFF & size >> 24); // 高位字节2
			out.write(low1);
			out.write(low2);
			out.write(hight1);
			out.write(hight2);
		} else if (len == 2) {
			byte low1 = (byte) (0xFF & size); // 低位字节1
			byte low2 = (byte) (0xFF & size >> 8); // 低位字节2
			out.write(low1);
			out.write(low2);
		}
	}

	/**
	 * 使用RSA加密随机生成的公钥并存储
	 * @author yzh
	 * @date 2020年6月1日
	 *  
	 * @param pKey
	 * @param out
	 * @return
	 * @throws Exception
	 */
	private static SignObject writeSignPublic(String pKey, OutputStream out) throws Exception {
		SignObject signObject = SecretSign.getSignObject();
		byte[] publicKey = signObject.getPublicKey();
		if (publicKey == null || publicKey.length == 0) {
			throw new ManagerException(Status.BuildExceptionStatus("500", "签名公钥生成有误"));
		}
		byte[] encrype = RSAEncryptUtil.encrype(publicKey, Base64.getDecoder().decode(pKey));
		// 写入加密公钥的字节数，2个字节
		writeSize(out, encrype.length, 2);
		// 写入公钥
		out.write(encrype);
		return signObject;
	}

	/**
	 * 写入des秘钥数据,并返回des秘钥
	 * @author yzh
	 * @throws Exception 
	 * @date 2020年5月28日
	 *
	 */
	private static void writeKeyData(String publicKey, byte[] desKey, OutputStream out) throws Exception {
		// 将秘钥进行rsa加密
		byte[] encrype2 = RSAEncryptUtil.encrype(desKey, Base64.getDecoder().decode(publicKey));
		int keySize = encrype2.length;
		// 秘钥长度为0，不需要存储秘钥,2个字节存储
		out.write((byte) (0xFF & keySize)); // 字节数
		out.write((byte) (0xFF & keySize >> 8)); // 字节数
		out.write(encrype2);
	}

}
